package dungeonElements;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import dungeon.Dungeon;
import dungeon.RoomHandler;
import dungeon.Shaft;
import dungeonTools.Coordinate;
import dungeonTools.Direction;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;

import temperature.Temperature;

/**
 * A class of teleportation squares. A teleportation square is a normal square with the 
 * ability to teleport an avatar to an other teleportation square. The destination 
 * square(s) doesn't have to be in the same dungeon. The destination square(s) doesn't 
 * have to teleport back to this square.
 * 
 * @invar A TeleportationSquare always has proper borders. This means:
 * 		-a teleportation square never has door between two squares of a shaft.
 * 		|hasProperBorders()
 * 
 * @author Tom Jacobs & Ewoud De Moor
 * @version 3.0
 */
public abstract class TeleportationSquare extends Square{

	/**
	 * Creates a new TeleportationSquare with the following attributes:
	 * Temperature - see param temperature
	 * Humidity - see param humidity
	 * Borders - see param northBorder, eastBorder, southBorder, 
	 * 						westBorder, ceilingBorder and floorBorder
	 * Slippery Surface - DEFAULT: false
	 * Terminated - DEFAULT: false
	 * 
	 * @param temperature
	 * 		The temperature this teleportation square will have.
	 * @param humidity
	 * 		The humidity level this teleportation square will have.
	 * @param northBorder
	 * 		Determines whether this teleportation square has a wall in the northern direction.
	 * @param eastBorder
	 * 		Determines whether this teleportation square has a wall in the eastern direction.
	 * @param southBorder
	 * 		Determines whether this teleportation square has a wall in the southern direction.
	 * @param westBorder
	 * 		Determines whether this teleportation square has a wall in the western direction.
	 * @param ceilingBorder
	 * 		Determines whether this teleportation square has a wall in the ceiling.
	 * @param floorBorder
	 * 		Determines whether this teleportation square has a wall in the floor.
	 * @effect This new teleportation square is initialized as a square with the 
	 * 		attributes defined above.
	 * 		| super(temperature, humidity, northBorder, eastBorder, southBorder, 
	 * 		|			westBorder, ceilingBorder, floorBorder)
	 */
	@Raw
	public TeleportationSquare(Temperature temperature, double humidity, 
			boolean northBorder, boolean eastBorder, boolean southBorder, 
			boolean westBorder, boolean ceilingBorder, boolean floorBorder) {
		super(temperature, humidity, northBorder, eastBorder, southBorder, 
				westBorder, ceilingBorder, floorBorder);
	}
	
	/**
	 * Creates a new default TeleportationSquare with the following attributes:
	 * Temperature - the DEFAULT_TEMPERATURE value
	 * Humidity - the Temperature.DEFAULT_HUMIDITY value
	 * Borders - DEFAULT: Walls in all directions
	 * Slippery Surface - DEFAULT: false
	 * Terminated - DEFAULT: false
	 * 
	 * @effect This new TeleportationSquare is initialized as a default square.
	 * 		| super()
	 */ 
	@Raw
	public TeleportationSquare() {
		super();
	}
	
	
	/**
	 * Check whether this TeleportationSquare can have the given border in the given 
	 * direction as its border.
	 * 
	 * @param border
	 * 		The border to check
	 * @param direction
	 * 		The direction to check
	 * @return False if direction is not initialized
	 * 		| if(direction == null)
	 * 		|	then result == false
	 * @return True if this TeleportationSquare is terminated and the border is set to nothing.
	 * 		| if(this.isTerminated && (border==null))
	 * 		|	then result == true
	 * @return False if the given border is a terminated border
	 * 		| if( (border!=null) && border.isTerminated())
	 * 		|	then result == false
	 * @return True if setting the border wouldn't change anything.
	 * 		| if( (getBorder(direction) != null && getBorder(direction).equals(border))
	 *		|		|| ((getBorder(direction)==null)&&(border==null)) )
	 * 		|	then result == true
	 * @return False if this TeleportationSquare is in a shaft and adding the door would result in a 
	 * 		door between squares of a shaft. 
	 * 		| if( (border instanceof Door) && Dungeon.isSquareInsideAnyDungeon(this) 
	 * 		|	&& (Dungeon.getDungeonContainingSquare(this) instanceof Shaft) ){
	 *		| 	let shaftContainingSquare be Dungeon.getDungeonContainingSquare(this)
	 *		| 	and let coordinateEnd be new Coordinate(shaftContainingSquare.getXSize(), shaftContainingSquare.getYSize(), shaftContainingSquare.getZSize())
	 *		|	and let coordinateThisSquare be shaftContainingSquare.getCoordinateOfSquare(this)
	 *		|	and let directionOfShaft be shaftContainingSquare.getDirection() in
	 *		|	if(	coordinateThisSquare.equals(Coordinate.ORIGIN) 
	 *		|			&& directionOfShaft.equals(direction.getOppositeDirection()) )
	 * 		| 		then result == false 
	 *		|	if(coordinateThisSquare.equals(coordinateEnd) 
	 *		|			&& directionOfShaft.equals(direction) )
	 * 		| 		then result == false
	 *		|	if( !coordinateThisSquare.equals(Coordinate.ORIGIN)
	 *		|			&& !coordinateThisSquare.equals(coordinateEnd)
	 *		|			&& (directionOfShaft.equals(direction) 
	 *		|				|| directionOfShaft.equals(direction.getOppositeDirection())) )
	 * 		| 		then result == false
	 */
	@Override @Raw
	public boolean canHaveAsBorderInDirection(@Raw Border border, Direction direction){
		if(direction == null)
			return false; //Direction not initialized
		if( this.isTerminated() && (border==null) )
			return true;
		if( (border!=null) && border.isTerminated())
			return false; //Can not have a terminated border
		if( (getBorder(direction) != null && getBorder(direction).equals(border))
				|| ((getBorder(direction)==null)&&(border==null)) )
			return true; //Current state
		
		//Check for doors between shaft squares
		if( (border instanceof Door) && Dungeon.isSquareInsideAnyDungeon(this) 
				&& (Dungeon.getDungeonContainingSquare(this) instanceof Shaft) ){
			Shaft<TeleportationSquare> shaftContainingSquare = (Shaft<TeleportationSquare>) Dungeon.getDungeonContainingSquare(this);
			Coordinate coordinateEnd = new Coordinate(shaftContainingSquare.getXSize()-1, shaftContainingSquare.getYSize()-1, shaftContainingSquare.getZSize()-1);
			Coordinate coordinateThisSquare = shaftContainingSquare.getCoordinateOfSquare(this);
			Direction directionOfShaft = shaftContainingSquare.getDirection();
			if(	coordinateThisSquare.equals(Coordinate.ORIGIN) 
					&& directionOfShaft.equals(direction.getOppositeDirection()) )
				return false; //Square at origin
			if(coordinateThisSquare.equals(coordinateEnd) 
					&& directionOfShaft.equals(direction) )
				return false; //Square at end
			if( !coordinateThisSquare.equals(Coordinate.ORIGIN)
					&& !coordinateThisSquare.equals(coordinateEnd)
					&& (directionOfShaft.equals(direction) 
							|| directionOfShaft.equals(direction.getOppositeDirection())) )
				return false; //Square in middle
		}
		return true;
	}
	
	/**
	 * Check whether this TeleportationSquare has proper borders.
	 * 
	 * @return False if in some direction this square has an border that isn't proper set 
	 * 		or is terminated.
	 * 		| for each direction in Direction:
	 *		|	if( (borders.get(direction) != null) 
	 *		|		&& (borders.get(direction).getSquareInDirection(direction.getOppositeDirection()) != this) )
	 *		|	then result == false
	 * 		|	if( (borders.get(direction) != null) && (borders.get(direction).isTerminated()) )
	 * 		|		then result == false
 	 * @return False if this TeleportationSquare has doors between squares of a shaft.
	 * 		| if( Dungeon.isSquareInsideAnyDungeon(this) 
	 * 		| 	&& (Dungeon.getDungeonContainingSquare(this) instanceof Shaft) )
	 * 		| 	let shaftContainingSquare be Dungeon.getDungeonContainingSquare(this)
	 * 		| 	let coordinateEnd be new Coordinate(shaftContainingSquare.getXSize()-1, shaftContainingSquare.getYSize()-1, shaftContainingSquare.getZSize()-1)
	 * 		| 	if( shaftContainingSquare.getCoordinateOfSquare(this).equals(Coordinate.ORIGIN) 
	 * 		| 		&& (getBorder(shaftContainingSquare.getDirection().getOppositeDirection()) instanceof Door) )
	 * 		| 		then result == false
	 * 		| 	if( shaftContainingSquare.getCoordinateOfSquare(this).equals(coordinateEnd) 
	 * 		| 		&& (getBorder(shaftContainingSquare.getDirection()) instanceof Door) )
	 * 		| 		then result == false
	 * 		|	if( !shaftContainingSquare.getCoordinateOfSquare(this).equals(Coordinate.ORIGIN)
	 * 		|		&& !shaftContainingSquare.getCoordinateOfSquare(this).equals(coordinateEnd)
	 * 		|		&&((getBorder(shaftContainingSquare.getDirection()) instanceof Door)
	 * 		|				|| (getBorder(shaftContainingSquare.getDirection().getOppositeDirection()) instanceof Door)) )
	 * 		| 		then result == false
	 */ 
	@Override @Raw
	public boolean hasProperBorders(){
		if(!super.hasProperBorders())
			return false;
		//Check for doors between shaft squares 
		if( Dungeon.isSquareInsideAnyDungeon(this) 
				&& (Dungeon.getDungeonContainingSquare(this) instanceof Shaft) ){
			Shaft<TeleportationSquare> shaftContainingSquare = (Shaft<TeleportationSquare>) Dungeon.getDungeonContainingSquare(this);
			Coordinate coordinateEnd = new Coordinate(shaftContainingSquare.getXSize()-1, shaftContainingSquare.getYSize()-1, shaftContainingSquare.getZSize()-1);
			if( shaftContainingSquare.getCoordinateOfSquare(this).equals(Coordinate.ORIGIN) 
					&& (getBorder(shaftContainingSquare.getDirection().getOppositeDirection()) instanceof Door) )
				return false; //Square at origin
			if( shaftContainingSquare.getCoordinateOfSquare(this).equals(coordinateEnd) 
					&& (getBorder(shaftContainingSquare.getDirection()) instanceof Door) )
				return false; //Square at end
			if( !shaftContainingSquare.getCoordinateOfSquare(this).equals(Coordinate.ORIGIN)
					&& !shaftContainingSquare.getCoordinateOfSquare(this).equals(coordinateEnd)
					&&((getBorder(shaftContainingSquare.getDirection()) instanceof Door)
							|| (getBorder(shaftContainingSquare.getDirection().getOppositeDirection()) instanceof Door)) )
				return false; //Square in middle
		}
		return true;	
	}
	
	
	/**
	 * returns a teleportation destination. If there are multiple teleportation 
	 * destinations, one of the destinations is chosen.
	 * 
	 * @pre This teleportation square must have at least one teleportation destination.
	 * 		| hasTeleportationDestination()
	 * @return A destination TeleportationSquare.
	 */
	public TeleportationSquare getTeleportationDestination(){
		assert hasTeleportationDestination();
		int randomIndex = randomGenerator.nextInt(teleportationDestinations.size());
		return teleportationDestinations.get(randomIndex);
	}
	
	/**
	 * A random generator used to chose the destination if there are multiple.
	 */
	private static Random randomGenerator = new Random();
	
	/**
	 * Returns a set with all the teleportation destinations. If there are no 
	 * destinations, an empty set is returned.
	 */
	@Basic
	public List<TeleportationSquare> getAllTeleportationDestinations(){
		return new ArrayList<TeleportationSquare>(teleportationDestinations);
	}
	
	/**
	 * Check if this teleportation square has teleportation destinations.
	 * 
	 * @return true if this teleportation square has at least one teleportation 
	 * 		destination.
	 * 		| result == !getAllTeleportationDestinations().isEmpty()
	 */
	public boolean hasTeleportationDestination(){
		if(teleportationDestinations.isEmpty())
			return false;
		return true;
	}
	
	/**
	 * Set the teleportation destinations to this teleporation square.
	 * 
	 * @param teleportableSquares
	 * 		The teleportation squares to set as teleportation destination squares.
	 * @pre The teleportation squares must be initialized.
	 * 		| for each teleportableSquare in teleportableSquares:
	 * 		| 	teleportableSquare != null
	 * @post This teleportation square has the given teleportation squares as 
	 * 		destination teleportation squares. The old destination teleportation 
	 * 		squares are removed.
	 * 		| for each teleportableSquare in teleportableSquares:
	 * 		|	getAllTeleportationDestinations().contains(teleportableSquare)
	 */
	public void setTeleportationDestination(TeleportationSquare... teleportableSquares){
		teleportationDestinations.clear();
		for(TeleportationSquare teleportableSquare: teleportableSquares ){
			assert teleportableSquare != null;
			teleportationDestinations.add(teleportableSquare);
		}
		if(Dungeon.isSquareInsideAnyDungeon(this))
			RoomHandler.enforceRoomBorders(this);
	}
	
	/**
	 * A list of destination teleportation squares.
	 */
	private ArrayList<TeleportationSquare> teleportationDestinations = new ArrayList<TeleportationSquare>();

}
